<?php
/**
 *
 *
 * @author Zeon <scplay@gmail.com>
 * @date 2018/6/26 14:45
 * @copyright PanxSoft Inc.
 */

namespace App\Services\Repositories\Product;


use App\Http\Controllers\Service\Visit\AppScanProductController;
use App\Http\Requests\Admin\Customer\Product\ProductStoreRequest;
use App\Http\Requests\Admin\Customer\Product\ProductUpdateRequest;
use App\Http\Requests\Admin\Store\ProductIndexRequest;
use App\Models\Customer\Customer;
use App\Models\Customer\DeviceAccount;
use App\Models\Customer\PackAssetPivotProduct;
use App\Models\Panx\BrandSeries;
use App\Models\Panx\Category;
use App\Models\Panx\Product;
use App\Models\Panx\Record;
use App\Models\Panx\RecordType;
use App\Models\Store\Store;
use App\Services\Utils\ApiChecker;
use App\Services\Utils\PanxCollection;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

class ProductRepository
{
    use ApiChecker;

    /**
     * destroy product model and it's clean relations
     *
     * @param Product $product
     *
     * @return bool
     * @throws \Exception
     */
    public static function destroyCleanRelations(Product $product): bool
    {
        // delete mod & SKU relations
        $product->attributeValues()->detach();
        $product->specValues()->detach();

        $product->bodyPositions()->detach();
        $product->productTags()->detach();

        // 删除关联的图片，不会触发 deleted 模型事件
        // @link https://coding.net/u/zeon/p/beauty-mirror/task/3803123 - #656 模型组要求不清理图片
        $product->productImages()->delete();

        // 删除整妆对应素材的商品关联
        PackAssetPivotProduct::cleanDeleteProduct($product->id);

        // 如此商品是客户的商品，清除店铺引用此商品的关联ID，（store_pivot_customer_product）
        $product->storePivotCustomerProductStores()->detach();

        // 如此商品是虚拟仓的商品，清除引用此商品的共享商品的关联ID
        $product->shareProducts()->update(['src_product_id' => 0]);

        $product->delete();

        return true;
    }

    /**
     * 后台店铺查看商品的列表
     *
     * @param Store $store
     * @param ProductIndexRequest $request
     * @return Product|\Illuminate\Database\Eloquent\Builder
     */
    public function adminStoreProductsQuery(Store $store, ProductIndexRequest $request)
    {
        $sort_column = $this->formatStoreProductSortColumn($request);

        $products = $this->joinSelectStorePvtCustomerProduct($store)
            ->filterStoreProductType($store, $request->product_type)
            ->columnsKeywordOrLike(['title', 'spu_code', 'name'], $request->keyword)
            ->filterBrand($request->brand_id)
            ->filterCategory($request->category_id)
            ->filterStoreIsSale($store->id, $request->is_sale)
//            ->filterStoreIsDeleted($request->is_deleted)
            ->filterIsHot($request->is_hot)
            ->filterIsNew($request->is_new)
            ->filterHasYouzan($request->has_youzan)
            ->with([
                'category', 'categoryDescriptionByLanguage',
                'thumbImage', 'colorThumbImage',
                'asset', 'productTags', 'brand',
                'brandSeries', 'cosmeticMaterial', 'bodyPositions',
            ])
            ->orderBy($sort_column, $request->sort_by ?: 'DESC');

        return $products;
    }

    /**
     * @param Store $store
     * @param int $category_id
     * @return Product[]|Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection
     */
    public function appStoreProductsForCreate(Store $store, int $category_id)
    {
        $store_customer_product_deleted_ids = self::getStoreCustomerDeletedProductIds($store);

        $products = (new Product)
            ->leftJoin('store_pivot_customer_product', function (JoinClause $join) use ($store) {
                $join->on('products.id', '=', 'store_pivot_customer_product.product_id')
                    ->where('store_pivot_customer_product.store_id', '=', $store->id);
            })
            ->where('category_id',$category_id)
            ->filterStoreProductType($store, Product::STORE_CUSTOMER_PRODUCT)
            ->whereNotIn('products.id', $store_customer_product_deleted_ids)
            ->filterOnShelf()
            ->select('brand_id','brand_series_id','products.id')
            ->get();

        return $products;
    }

    /**
     * app端设备账号筛选商品 (默认显示customer的商品)
     *
     * @param Store $store
     * @param \App\Http\Requests\App\ProductIndexRequest $request
     * @return Builder
     */
    public function appStoreProductsQuery(Store $store, $request)
    {
        $store_customer_product_deleted_ids = self::getStoreCustomerDeletedProductIds($store);

        $products = $this->joinSelectStorePvtCustomerProduct($store)
            ->filterStoreProductType($store, Product::STORE_CUSTOMER_PRODUCT)
            ->whereNotIn('products.id', $store_customer_product_deleted_ids)
            ->whereHas('brand') // 数据库中有没有品牌 id 的错误数据
            ->filterBrand($request->brand_id)
            ->filterBrandSeries($request->brand_series_id)
            ->filterOnShelf()
            ->where('category_id', $request->category_id)
            ->with([
                'asset', 'thumbImage', 'productTags',
                'brand.brandSeriesBySortLatest',
                'productUrlType'
            ])

            ->orderBy('sort_priority', 'DESC')
            ->filterBrand($request->brand_id)
            ->filterBrandSeries($request->brand_series_id);

        return $products;
    }

    /**
     * 店铺上架商品的商品ids
     * @param Store $store
     * @return array
     */
    public static function getStoreCustomerProductIds(Store $store): array
    {
        $store_customer_product_ids = DB::table('store_pivot_customer_product')
            ->where('is_deleted', Product::IS_NO_DELETE)
            ->where('store_id', $store->id)
            ->pluck('product_id')
            ->all();

        return $store_customer_product_ids;
    }

    /**
     * 已经被店铺下架的商家商品ids
     * @param Store $store
     * @return array
     */
    public static function getStoreCustomerDeletedProductIds(Store $store): array
    {
        // 店铺正常下架的商家商品ids
        $store_customer_product_ids = DB::table('store_pivot_customer_product')
            ->where('is_sale', Product::IS_NO_SALE)
            ->where('store_id', $store->id)
            ->pluck('product_id')
            ->all();

        return $store_customer_product_ids;
    }


    /**
     * @param Store $store
     *
     * @return Product
     */
    private function joinSelectStorePvtCustomerProduct(Store $store): Builder
    {
        return (new Product)
            // 此处为关联显示商家的显示， 还需要用关联表上 priority is_deleted 字段排序，过滤
            ->leftJoin('store_pivot_customer_product', function (JoinClause $join) use ($store) {
                $join->on('products.id', '=', 'store_pivot_customer_product.product_id')
                    ->where('store_pivot_customer_product.store_id', '=', $store->id);
            })
            // products 表上的 try_on 字段表示所有店铺在同一商品上的试戴总次数，
            // 此处为通过连接 records 表成组，计算商品在店铺中对应所有设备的试戴合计次数，并需要根据 try_on 排序
            ->leftJoinSub(
                $this->storeProductTryOnRecordCountGroupBy($store), 'records',
                'products.id', '=', 'records.product_id'
            )
            ->select([
//                '*',

                'youzan_item_id', 'name', 'title', 'brand_id', 'brand_series_id',
                'category_id', 'cosmetic_material_id', 'market_price', 'sell_price',
                'brand_series_id', 'product_url_type_id', 'product_url', 'asset_id',
                'customer_id', 'spu_code',

                'products.id as id',
                'products.priority as priority',
                'products.is_sale as is_sale',
                'products.store_id as store_id',
                'products.created_at as created_at',
                'products.updated_at as updated_at',
                'store_pivot_customer_product.priority as store_pivot_customer_product_priority',
                'store_pivot_customer_product.is_sale as store_pivot_customer_product_is_sale',
                'store_pivot_customer_product.store_id as store_pivot_customer_product_store_id',
                'store_pivot_customer_product.id as store_pivot_customer_product_id',

                // 以关联表值优先，如关联表值为 null（不存在关联数据）时则使用 products 表中原值
                DB::raw('coalesce(store_pivot_customer_product.priority, products.priority) as sort_priority'),
                DB::raw('coalesce(store_pivot_customer_product.is_sale, products.is_sale) as show_is_sale'),
//                DB::raw($this->caseStoreProductPvtCustomerIsSaleSql()),

                // 处理 records 表中不存在对应的 product_id 试戴次数会变成 null 的情况
                DB::raw("coalesce(records.try_on, 0) as try_on"),
            ]);
    }

    /**
     * @param ProductIndexRequest $request
     * @return string
     */
    private function formatStoreProductSortColumn(ProductIndexRequest $request): string
    {
        $sort_column = $request->sort_column ?: 'products.created_at';

        if ($sort_column === 'priority') $sort_column = 'sort_priority';
        if ($sort_column === 'created_at') $sort_column = 'products.created_at';
        /**
         * @uses storeProductTryOnRecordCountGroupBy()
         */
        if ($sort_column === 'try_on') $sort_column = 'records.try_on';

        return $sort_column;
    }

    /**
     * @test
     *
     * @param int $product_id
     * @param int $device_account_id
     *
     * @return string
     */
    public function getAppConvertUrl(int $product_id, int $device_account_id): string
    {
        return route(
            AppScanProductController::ROUTES['invoke'],
            compact('product_id', 'device_account_id')
        );
    }

    /**
     * 根据店铺的设备ID查找对应的试戴记录，并以记录ID（产品）成组计算数量与 product 表连接，可用于试戴排序
     *
     * @link https://laravel-news.com/laravel-5-6-17 - joinSub 的用法
     *
     * @param Store $store
     *
     * @return Builder
     */
    private function storeProductTryOnRecordCountGroupBy(Store $store): Builder
    {
        $dev_acnt_ids = $store->deviceAccounts()->pluck('id')->all();

        return (new Record)
            ->select([
                'objectable_id AS product_id',
                DB::raw('COUNT(*) AS try_on')
            ])
            ->where([
                'recorderable_type' => class_basename(DeviceAccount::class),
                'objectable_type'   => class_basename(Product::class),
            ])
            ->whereIn('recorderable_id', $dev_acnt_ids)
            ->groupBy('objectable_id');
    }

    /**
     * 统计商家的商品试戴数据
     *
     * @param Customer $customer
     * @return $this|PanxCollection
     */
    public function countCustomerTryOn(Customer $customer): Collection
    {
        $products = $this->getCustomerProductsLatestByTryOn($customer);

//        $all_dev_acnt_ids = $customer->deviceAccounts()->pluck('id')->all();
        $customer_product_ids = $products->pluck('id')->all();

        $dev_acnt_tryon_records = $this->getTryonRecordsGroupByDevAcntProductWithDevAcntAndStore($customer_product_ids);

        $prod_store_dev_acnt_groups = $dev_acnt_tryon_records
            ->groupBy('product_id')
            ->map(function (Collection $product_group) {
                return $product_group->groupBy('store_id');
            });

        $products->each(function (Product $product) use ($prod_store_dev_acnt_groups) {
            /** @var Collection $prod_store_dev_acnt_groups */
            $store_dev_acnt_groups = $prod_store_dev_acnt_groups[$product->id];

            $product->stores = $store_dev_acnt_groups->map(function (Collection $dev_acnts, $store_id) {
                return [
                    'store_id' => $store_id,
                    'name'     => $dev_acnts->first()->store_name,
                    'try_on'   => $dev_acnts->sum('try_on'),
//                    'count_device_accounts' => $dev_acnts->values(),
                ];
            })
                ->sortByDesc('try_on')// must sort by desc after sum
                ->values();
        });

        return $products;
    }


    /**
     * 统计店铺下所有机器的试戴数据
     * @param Store $store
     * @return Collection
     */
    public function countStoreTryOn(Store $store): Collection
    {
        $store_dev_acnt_ids = $store->deviceAccounts()->pluck('id')->all();

        $records_desc_by_tryon = $this->getTryonRecordsGroupByDevAcntProductWithDevAcnt($store_dev_acnt_ids);

        $tryon_records_group_by_product = $records_desc_by_tryon
            ->groupBy('product_id')
            ->take(20);

        $store_product_ids = $tryon_records_group_by_product->keys()->all();

        $products = $this->getStoreProductsLatestByTryOn($store_product_ids);

        $products->each(function (Product $product) use ($tryon_records_group_by_product) {
            $product->store_count_device_accounts = $tryon_records_group_by_product[$product->id];
            $product->try_on = $product->store_count_device_accounts->sum('try_on');
        });

        return $products->sortByDesc('try_on')->values();
    }

    /**
     * @param Customer $customer
     *
     * @return PanxCollection
     */
    protected function getCustomerProductsLatestByTryOn(Customer $customer)
    {
        return (new PanxCollection($customer->selfProducts()
            /**
             * @see \App\Services\Repositories\Device\DeviceAccountRepository::recordsProductTryOn()
             */
            ->latest('try_on')
            ->take(20)
            ->where([
                ['try_on', '>', 0],
//                ['is_sale', '=', Product::IS_SALE] // 暂时不排除下架的商品
            ])
            ->with('thumbImage')
            ->get(['id', 'name', 'category_id', 'brand_id', 'try_on', 'title'])))
            ->eachAppend(['brand_name', 'category_name']);
    }

    /**
     * @param Customer $customer
     *
     * @return Collection
     */
    protected function getCustomerStoresWithDevAcnts(Customer $customer): Collection
    {
        return $customer->stores()
            ->select(['id', 'name'])
            ->with(['deviceAccounts' => function (HasMany $hasMany) {
                $hasMany->select(['id', 'device_name', 'store_id']);
            }])
            ->get();
    }

    private function getTryonRecordsGroupByDevAcntProductWithDevAcntAndStore(array $customer_product_ids)
    {
        return (new Record)
            ->select([
                'recorderable_id AS device_account_id',
                'objectable_id AS product_id',
                // join dev acnt
                DB::raw('MAX(device_accounts.store_id) AS store_id'),
                DB::raw('MAX(device_accounts.device_name) AS device_name'),
                // join stores
                DB::raw('MAX(stores.name) AS store_name'),
                // sum tryon
                DB::raw('COUNT(*) AS try_on')
            ])
            ->leftJoin('device_accounts', 'device_accounts.id', '=', 'recorderable_id')
            ->leftJoin('stores', 'stores.id', '=', 'device_accounts.store_id')
//            ->whereIn('recorderable_id', $all_dev_acnt_ids)
            ->whereIn('objectable_id', $customer_product_ids)
            ->where([
                'recorderable_type' => class_basename(DeviceAccount::class),
                'record_type_id'    => RecordType::PRODUCT_TRYON
            ])
            ->groupBy('recorderable_id', 'objectable_id')
            // sort dev acnt tryon sum desc use sql
            ->latest('try_on')
            ->get();
    }

    private function getTryonRecordsGroupByDevAcntProductWithDevAcnt(array $store_dev_acnt_ids)
    {
        return (new Record)
            ->select([
                'recorderable_id AS device_account_id',
                'objectable_id AS product_id',
                // join dev acnt
                DB::raw('MAX(device_accounts.device_name) AS device_name'),
                // sum tryon
                DB::raw('COUNT(*) AS try_on')
            ])
            ->leftJoin('device_accounts', 'device_accounts.id', '=', 'recorderable_id')
            ->whereIn('recorderable_id', $store_dev_acnt_ids)
            ->where([
                'recorderable_type' => class_basename(DeviceAccount::class),
                'record_type_id'    => RecordType::PRODUCT_TRYON
            ])
            ->groupBy('recorderable_id', 'objectable_id')
            // sort dev acnt tryon sum desc use sql
            ->latest('try_on')
            ->get();
    }

    /**
     * @param $store_product_ids
     * @return PanxCollection
     */
    private function getStoreProductsLatestByTryOn($store_product_ids)
    {
        // TODO 是否要排除下架的商品
        return $products = (new PanxCollection((new Product())
            ->whereIn('id', $store_product_ids)
            ->with('thumbImage')
            ->get(['id', 'name', 'category_id', 'brand_id', 'try_on', 'title'])))
            ->eachAppend(['brand_name', 'category_name']);
    }

    /**
     * 仅供参考， show_is_sale 字段能满足当前需求，这里只展示下如何 CASE 替换原有字段
     *
     * @return string
     */
    private function caseStoreProductPvtCustomerIsSaleSql(): string
    {
        // 当 store_id 为 0， 是客户的产品，应该显示关联表的 is_sale， 如关联表没有数据则默认为 1 上架
        // 当 store_id 不为 0, 是店铺自己的产品，用产品表自身的 is_sale 即可
        return <<<SQL
            -- noinspection SqlNoDataSourceInspectionForFile
            CASE
            WHEN products.store_id = 0 THEN
                COALESCE(store_pivot_customer_product.is_sale, 1)
            ELSE
                products.is_sale
            END as is_sale
SQL;
    }

    /**
     *
     * 组装商品新增、更新的数据
     *
     * @param Request|ProductStoreRequest|ProductUpdateRequest $request
     * @param int $customer_id
     * @param int $store_id
     * @return array
     */
    public function assembleProductDetail(Request $request, int $customer_id, int $store_id = 0): array
    {
        $detail = [
            'customer_id'         => $customer_id,
            'store_id'            => $store_id,
            'name'                => $request->name,
            'title'               => $request->title,
            'category_id'         => $request->category_id,
            'brand_id'            => $request->brand_id,
            'product_mod_id'      => $request->product_mod_id ?? Product::NO_MOD_ID,
            'sell_price'          => $request->sell_price,
            'market_price'        => $request->market_price,
            'spu_code'            => $request->spu_code,
            'stock'               => $request->stock ?: 0,
            'content'             => $request->get('content'),
            'priority'            => $request->priority ?? 0,
            'product_url_type_id' => $request->product_url_type_id ?? 0,
            'sex'                 => $request->sex ?? 0,
            'brand_series_id'     => Category::isCosmetic($request->category_id)
                ? $request->brand_series_id
                : BrandSeries::ID_OTHER
        ];

        if ($request->asset_id) {
            $detail['asset_id'] = $request->asset_id;
        }

        if ($request->description) {
            $detail['description'] = $request->description;
        }

        if ($request->product_url_type_id) {
            $detail['product_url'] = $request->product_url;
        } else {
            $detail['product_url'] = null;
        }

        if ($request->has('is_deleted')) {
            $detail['is_deleted'] = $request->is_deleted;
        }

        if ($request->has('is_sale')) {
            $detail['is_sale'] = $request->is_sale;
        }

        /** @deprecated 去掉商品材质信息 */
        if ($request->has('cosmetic_material_id')) {
            $detail['cosmetic_material_id'] = $request->cosmetic_material_id;
        }

        return $detail;
    }

    /**
     * @param Product $product
     * @param array $product_data
     *
     * @return array
     */
    public function unsetUpdateProductDetail(Product $product, array $product_data): array
    {
        if ($product->youzan_item_id){ // 有赞商品不能修改外链类型及链接地址
            unset($product_data['product_url_type_id'], $product_data['product_url']);
        }

        return $product_data;
    }

    /**
     * @param Product $product
     * @param array|null $product_tag_ids
     * @return array
     */
    public function syncProductTags(Product $product, ?array $product_tag_ids): array
    {
        $sync_ids = [];
        if (is_array($product_tag_ids)) {
            // filter out null and zero values
            $sync_ids = array_values(array_filter($product_tag_ids));
        }

        $changes = $product->ProductTags()->sync($sync_ids);

        return $changes;
    }

    /**
     * @param Product $product
     * @param array|null $body_position_ids
     *
     * @return array
     */
    public function syncProductPivotBodyPosition(Product $product,
                                                  ?array $body_position_ids): array
    {
        if (! is_array($body_position_ids)) $body_position_ids = [];

        $changes = $product->bodyPositions()->sync(array_values($body_position_ids));

        return $changes;
    }

    /**
     * 检查商品上架的条件
     * @param Product $product
     * @throws \Exception
     */
    public function checkSaleCondition(Product $product) :void
    {
        $this->checkIsTrue($product->category_id, trans('admin.category_id')); // 验证 商品必须有category_id
        $this->checkIsTrue($product->brand_id, trans('admin.brand_id')); // 验证 商品必须有brand_id
    }

    /**
     * @param LengthAwarePaginator $products
     * @return LengthAwarePaginator
     */
    public function transformProducts(LengthAwarePaginator $products)
    {
        $products->transform(function(Product $product){
            $product->append('category_name');//dd($product->category->toArray());
            ($category = $product->category) && $category->name = $product->category_name;

            return $product;
        });

        return $products;
    }

}